home *** CD-ROM | disk | FTP | other *** search
Wrap
// BEGIN FLOCK GPL // // Copyright Flock Inc. 2005-2007 // http://flock.com // // This file may be used under the terms of of the // GNU General Public License Version 2 or later (the "GPL"), // http://www.gnu.org/licenses/gpl.html // // Software distributed under the License is distributed on an "AS IS" basis, // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License // for the specific language governing rights and limitations under the // License. // // END FLOCK GPL // const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; const CLASS_ID = Components.ID ('{5c54771f-2628-4200-af16-f94609177abd}'); const CLASS_NAME = "Flock Delicious Service"; const CONTRACT_ID = "@flock.com/delicious-service;1"; const PREFERENCES_CONTRACTID = "@mozilla.org/preferences-service;1"; const SERVICE_ENABLED_PREF = "flock.service.delicious.enabled"; const CATEGORY_COMPONENT_NAME = "Delicious JS Component" const CATEGORY_ENTRY_NAME = "delicious" const RDFS = Cc['@mozilla.org/rdf/rdf-service;1'].getService (Ci.nsIRDFService); const PREFS = Components.classes[PREFERENCES_CONTRACTID].getService(Ci.nsIPrefBranch); // migration constants const WEBSERVICE_PREF = "flock.favorites.webservice.id"; const OLD_DELICIOUS_PW_HOST = ":favorites:webservice:delicious:"; var gCompTK; function getCompTK() { if (!gCompTK) { gCompTK = Cc["@flock.com/singleton;1"].getService(Ci.flockISingleton) .getSingleton("chrome://browser/content/flock/services/common/load-compTK.js") .wrappedJSObject; } return gCompTK; } var gTimers = []; // For use with the scheduler const DELICIOUS_FAVICON = 'http://del.icio.us/favicon.ico'; function loadLibraryFromSpec(aSpec) { var loader = Cc['@mozilla.org/moz/jssubscript-loader;1'] .getService(Ci.mozIJSSubScriptLoader); loader.loadSubScript(aSpec); } loadLibraryFromSpec("chrome://browser/content/flock/favorites/deliciousApi.js"); loadLibraryFromSpec("chrome://browser/content/flock/favorites/onlineFavoritesBackend.js"); function flockDeliciousService() { var obs = Cc["@mozilla.org/observer-service;1"].getService (Ci.nsIObserverService); obs.addObserver (this, 'xpcom-shutdown', false); this._sync_worker_timer = null; this._state = 'idle'; this.status = Ci.flockIWebService.STATUS_UNKNOWN; this.acUtils = Cc["@flock.com/account-utils;1"].getService(Ci.flockIAccountUtils); this.url = "http://del.icio.us"; this.api = new DeliciousAPI('https://api.del.icio.us/v1/'); this.mIsInitialized = false; this._ctk = { interfaces: [ "nsISupports", "nsISupportsCString", "nsIClassInfo", "nsIObserver", "nsITimerCallback", "flockIWebService", "flockIManageableWebService", "flockIBookmarkWebService", "flockIPollingService", "flockIMigratable", ], shortName: "delicious", fullName: "Del.icio.us", description: CLASS_NAME, favicon: DELICIOUS_FAVICON, CID: CLASS_ID, contractID: CONTRACT_ID, accountClass: flockDeliciousAccount }; this._logger = Cc["@flock.com/logger;1"].createInstance(Ci.flockILogger); this._logger.init ("delicious"); this._profiler = Cc["@flock.com/profiler;1"].getService(Ci.flockIProfiler); this.init(); } flockDeliciousService.prototype.init = function flockDeliciousService_init() { // Prevent re-entry if (this.mIsInitialized) return; this.mIsInitialized = true; var evtID = this._profiler.profileEventStart("delicious-init"); this._logger.info (".init()"); // Determine whether this service has been disabled, and unregister if so. this.prefService = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); if ( this.prefService.getPrefType(SERVICE_ENABLED_PREF) && !this.prefService.getBoolPref(SERVICE_ENABLED_PREF) ) { this._logger.info("Pref "+SERVICE_ENABLED_PREF+" set to FALSE... not initializing."); var catMgr = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); catMgr.deleteCategoryEntry("wsm-startup", CATEGORY_COMPONENT_NAME, true); catMgr.deleteCategoryEntry("flockMigratable", CATEGORY_COMPONENT_NAME, true); catMgr.deleteCategoryEntry("flockWebService", CATEGORY_ENTRY_NAME, true); return; } // get the favorites service var fsvc = Cc['@mozilla.org/rdf/datasource;1?name=flock-favorites'].getService (Ci.flockIFavoritesService); fsvc.QueryInterface (Ci.nsIRDFDataSource); // get a coop interface to it this._coop = Cc['@flock.com/singleton;1'].getService(Ci.flockISingleton).getSingleton('chrome://browser/content/flock/common/load-faves-coop.js').wrappedJSObject; if (this._coop.Service.exists ('urn:delicious:service')) { this._service = this._coop.get ('urn:delicious:service'); } else { this._service = new this._coop.Service ('urn:delicious:service', { name: 'delicious', desc: 'del.icio.us' }); } this._service.serviceId = CONTRACT_ID; this._service.logoutOption = false; // Load Web Detective file this.webDetective = this.acUtils.useWebDetective("delicious.xml"); this._service.domains = this.webDetective.getString("delicious", "domains", "icio.us"); // Now that we know the service URN, we can register this service // with the Web Service Manager. this.urn = this._service.id(); this.bookmarksRootUrn = "urn:delicious:bookmarks:"; this._profiler.profileEventEnd(evtID, ""); } // BEGIN flockIWebService interface flockDeliciousService.prototype.title = 'del.icio.us'; flockDeliciousService.prototype.icon = DELICIOUS_FAVICON; flockDeliciousService.prototype.addAccountById = function (aAccountID, aIsTransient, aListener) { this._logger.debug("{flockIWebService}.addAccountById('"+aAccountID+"', "+aIsTransient+", aListener)"); var acct = onlineFavoritesBackend.createAccount(this, aAccountID, aIsTransient); if (aListener) aListener.onSuccess(acct, "addAccount"); return acct; } flockDeliciousService.prototype.removeAccount = function (aUrn) { this._logger.debug("{flockIWebService}.removeAccount('"+aUrn+"')"); onlineFavoritesBackend.removeAccount(this, aUrn); } // END flockIWebService interface // BEGIN flockIManageableWebService interface flockDeliciousService.prototype.updateAccountStatusFromDocument = function (aDocument) { this._logger.debug("{flockIManageableWebService}.updateAccountStatusFromDocument()"); if (this.webDetective.detect("delicious", "loggedout", aDocument, null)) { this.acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID); } else if (this.webDetective.detect("delicious", "loggedin", aDocument, null)) { var results = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag2); if (this.webDetective.detect("delicious", "accountinfo", aDocument, results)) { var accountID = results.getPropertyAsAString("accountid"); var accountURN = this.acUtils.getAccountURNById(this.urn, accountID); this.acUtils.ensureOnlyAuthenticatedAccount(accountURN); } } } flockDeliciousService.prototype.logout = function() { this._logger.debug("{flockIWebServiceService}.logout()"); var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"] .getService(Components.interfaces.nsICookieManager); cookieManager.remove(".del.icio.us", "_user", "/", false); } // END flockIManageableWebService interface flockDeliciousService.prototype._getAllTags = function (aUrn, aAccountId, aPassword, aListener) { this._logger.info ('getAllTags(...)\n'); var svc = this; this.api.tagsGet ({ onError: function () { // FIXME: report error? /* BUG: 5705 */ svc._logger.error ('Delicious tags/get failed\n'); if (aListener) aListener.onError (); }, onSuccess: function (aResult, aTopic) { // Delete existing tags and insert the new ones - there is probably a more efficient way to do it var tags = []; for (var i=0; i<aResult.length; i++) { if (aResult[i].tag != "system:unfiled") tags.push(aResult[i].tag); } onlineFavoritesBackend.updateTags(svc, aAccountId, tags); // Tell the poller we're done if (aListener) aListener.onResult(); } }); } // flockIBookmarkWebService flockDeliciousService.prototype.publish = function (aListener, aAccountId, aBookmark, aPrivate) { this._logger.info("Publish ("+aBookmark.URL+","+aBookmark.name+") to "+aAccountId+"@Delicious\n"); var accountUrn = this.urn+':'+aAccountId; var password = this.acUtils.getPassword (accountUrn); var svc = this; var tags = aBookmark.tags; tags = tags?tags.split(/[\s,]/).sort().join(' ').replace(/^ /, ''):''; var args = { url: aBookmark.URL, description: aBookmark.name, tags: tags, extended: aBookmark.description }; if (aPrivate) args.shared = 'no'; this.api.call ('posts/add', args, { onError: function () { // FIXME: report error? /* BUG: 5705 */ svc._logger.error ('Delicious posts/add failed\n'); if (aListener) aListener.onError (null, null, null); }, onSuccess: function (aXML) { if (!aXML || !aXML.documentElement || aXML.documentElement.tagName != 'result') { // bad posts/all response // FIXME: report error? /* BUG: 5705 */ svc._logger.error ('Delicious posts/add failed - invalid xml response'); if (aListener) aListener.onError (null, null, null); return; } var result = aXML.documentElement.getAttribute("code"); svc._logger.debug ("Result to posts/add: "+result); if (result == "done") { var localBookmarks = svc._coop.get(svc.urn+":"+aAccountId+":bookmarks"); var tagslist = aBookmark.tags.split(" "); for (i in tagslist) localBookmarks.tag.addOnce(tagslist[i]); onlineFavoritesBackend.updateBookmark (svc, accountUrn, aBookmark, aPrivate); if (aListener) aListener.onSuccess(null, null); } else { if (aListener) aListener.onError(null, null, null); } } }, password); } // flockIBookmarkWebService flockDeliciousService.prototype.publishList = function (aListener, aAccountId, aBookmarkList, aPrivate) { var delay = 1000; // 1 second between each query var svc = this; this._publishTimer = []; var i = 0; var sync = []; var bookmarks = []; var sync = { notify: function (timer) { var bm = bookmarks.shift(); svc.publish(aListener, aAccountId, bm, aPrivate); } } while (aBookmarkList.hasMoreElements()) { var bookmark = aBookmarkList.getNext().QueryInterface(Ci.flockIBookmark); // Duplicate it because it's going to be removed too early bookmarks[i] = {}; bookmarks[i].URL = bookmark.URL; bookmarks[i].name = bookmark.name; bookmarks[i].description = bookmark.description; bookmarks[i].tags = bookmark.tags; bookmarks[i].time = bookmark.time; this._logger.debug("==== Set a timer to "+i*delay+" for "+bookmarks[i].URL+"\n"); this._publishTimer[i] = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer); this._publishTimer[i].initWithCallback(sync, i * delay, Ci.nsITimer.TYPE_ONE_SHOT); i++; } } // flockIBookmarkWebService flockDeliciousService.prototype.remove = function (aListener, aAccountId, aUrl) { this._logger.info("Remove "+aUrl+" from "+aAccountId+"@Delicious\n"); var password = this.acUtils.getPassword (this.urn+':'+aAccountId); var svc = this; var args = { url: aUrl }; this.api.call ('posts/delete', args, { onError: function () { // FIXME: report error? /* BUG: 5705 */ svc._logger.error ('Delicious posts/delete failed\n'); if (aListener) aListener.onError (null, null, null); }, onSuccess: function (aXML) { if (!aXML || !aXML.documentElement || aXML.documentElement.tagName != 'result') { // bad posts/all response // FIXME: report error? /* BUG: 5705 */ svc._logger.error ('Delicious posts/add failed - invalid xml response'); if (aListener) aListener.onError (null, null, null); return; } var result = aXML.documentElement.getAttribute("code"); svc._logger.debug ("Result to posts/add: "+result); if (result == "done") { onlineFavoritesBackend.destroyBookmark (svc, aAccountId, aUrl); Cc["@flock.com/poller-service;1"].getService(Ci.flockIPollerService) .forceRefresh(this.urn+":"+aAccountId+":bookmarks"); if (aListener) aListener.onSuccess(null, null); } else { if (aListener) aListener.onError(null, null, null); } } }, password); } // flockIBookmarkWebService flockDeliciousService.prototype.exists = function (aAccountId, aURL) { return this._coop.Bookmark.exists (this.urn+":"+aAccountId+":"+aURL); } flockDeliciousService.prototype.getUserUrl = function (aAccountId) { return "http://del.icio.us/"+aAccountId; } // flockIPollingService flockDeliciousService.prototype.refresh = function (aURN, aListener) { var svc = this; this._logger.info ('refresh (' + aURN + ')'); if (!this._coop.OnlineBookmarksStream.exists (aURN)) throw "flockDeliciousService: "+aURN+" can't be found"; var bookmarks = this._coop.get (aURN); var accountId = bookmarks.userid; var accountUrn = this.urn + ":" + accountId; // nsIPassword for auth for this sync var password = this.acUtils.getPassword (accountUrn); this.api.postsAllIfNewer({ onError: function (aError) { svc._logger.error ('Delicious (Delicious API) posts/all failed: [' +aError.errorCode+"] "+aError.errorString+" ([" +aError.serviceErrorCode+"] "+ aError.serviceErrorString+")"); var message; switch (aError.errorCode) { case aError.FAVS_UNAVAILABLE: message = "Del.icio.us reported that the service is currently unavailable; your bookmarks cannot be updated. Contact Delicious for more informations."; break; case aError.FAVS_INVALID_AUTH: message = "Del.icio.us reported that your username or password is wrong. Please log in to Del.icio.us again to update your authentication information."; break; default: message = "Delicious returned an error: ["+aError.serviceErrorCode+"] "+aError.serviceErrorString; } // need this delay for the migration case // see https://bugzilla.flock.com/show_bug.cgi?id=9051 var sync = { notify: function (timer) { onlineFavoritesBackend.showNotification(message); } } svc._timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer); svc._timer.initWithCallback(sync, 1000, Ci.nsITimer.TYPE_ONE_SHOT); if (aListener) aListener.onError(aError); }, onSuccess: function (aSubject, aTopic) { if (aTopic == "updated") { onlineFavoritesBackend.updateLocal(svc, aSubject, accountUrn); // Now refresh the tag list (wait one sec) var sync = { notify: function (timer) { svc._getAllTags (aURN, accountId, password, aListener); } } svc._timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer); svc._timer.initWithCallback(sync, 1000, Ci.nsITimer.TYPE_ONE_SHOT); } else { // Tell the poller we're done if (aListener) aListener.onResult(); } } } , password, bookmarks.last_update_time); } // nsIObserver flockDeliciousService.prototype.observe = function (subject, topic, state) { switch (topic) { case 'xpcom-shutdown': var obs = Cc["@mozilla.org/observer-service;1"] .getService (Ci.nsIObserverService); obs.removeObserver (this, 'xpcom-shutdown'); return; } } /****************************************************************************** * XPCOM Functions for construction and registration ******************************************************************************/ function createModule(aParams) { return { registerSelf: function (aCompMgr, aFileSpec, aLocation, aType) { aCompMgr.QueryInterface(Ci.nsIComponentRegistrar); aCompMgr.registerFactoryLocation( aParams.CID, aParams.componentName, aParams.contractID, aFileSpec, aLocation, aType ); var catMgr = Cc["@mozilla.org/categorymanager;1"] .getService(Ci.nsICategoryManager); if (!aParams.categories) { aParams.categories = []; } for (var i = 0; i < aParams.categories.length; i++) { var cat = aParams.categories[i]; catMgr.addCategoryEntry( cat.category, cat.entry, cat.value, true, true ); } }, getClassObject: function (aCompMgr, aCID, aIID) { if (!aCID.equals(aParams.CID)) { throw Cr.NS_ERROR_NO_INTERFACE; } if (!aIID.equals(Ci.nsIFactory)) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; } return { // Factory createInstance: function (aOuter, aIID) { if (aOuter != null) { throw Cr.NS_ERROR_NO_AGGREGATION; } var comp = new aParams.componentClass(); if (aParams.implementationFunc) { aParams.implementationFunc(comp); } return comp.QueryInterface(aIID); } }; }, canUnload: function (aCompMgr) { return true; } }; } // NS Module entrypoint function NSGetModule(aCompMgr, aFileSpec) { return createModule({ componentClass: flockDeliciousService, CID: CLASS_ID, contractID: CONTRACT_ID, componentName: CATEGORY_COMPONENT_NAME, implementationFunc: function (aComp) { getCompTK().addAllInterfaces(aComp); }, categories: [ { category: "wsm-startup", entry: CATEGORY_COMPONENT_NAME, value: CONTRACT_ID }, { category: "flockMigratable", entry: CATEGORY_COMPONENT_NAME, value: CONTRACT_ID }, { category: "flockWebService", entry: CATEGORY_ENTRY_NAME, value: CONTRACT_ID } ] }); } // ======================================================= // ========== BEGIN flockDeliciousAccount class ========== // ======================================================= function flockDeliciousAccount() { this._coop = Cc['@flock.com/singleton;1'].getService(Ci.flockISingleton) .getSingleton('chrome://browser/content/flock/common/load-faves-coop.js') .wrappedJSObject; this.acUtils = Cc["@flock.com/account-utils;1"].getService(Ci.flockIAccountUtils); this.service = Cc[CONTRACT_ID].getService(Ci.flockIWebService); this._ctk = { interfaces: [ "nsISupports", "flockIWebServiceAccount", "flockIBookmarkWebServiceAccount" ] }; this._logger = Cc["@flock.com/logger;1"].createInstance (Ci.flockILogger); this._logger.init ("delicious"); getCompTK().addAllInterfaces(this); } // BEGIN flockIWebServiceAccount interface flockDeliciousAccount.prototype.activate = function flockDeliciousAccount_activate(aListener) { this._logger.debug("{flockIWebServiceAccount}.activate()"); this._coop.get(this.urn).isAuthenticated = true; this._coop.get(this.urn + ':bookmarks').isPollable = true; if (aListener) { aListener.onSuccess(this, "activate"); } } flockDeliciousAccount.prototype.keep = function flockDeliciousAccount_keep(aListener) { this._logger.debug("{flockIWebServiceAccount}.keep()"); this._coop.get(this.urn).isTransient = false; this._coop.get(this.urn+":bookmarks").isTransient = false; this.acUtils.makeTempPasswordPermanent(this.urn); if (aListener) { aListener.onSuccess(this, "keep"); } } // END flockIWebServiceAccount interface // BEGIN flockIMigratable flockDeliciousService.prototype.__defineGetter__('shortname', function() { return "del.icio.us"; }); flockDeliciousService.prototype.needsMigration = function flockDeliciousService_needsMigration(oldVersion) { // if online bookmarks webservice is configured AND using delicious var pw = this.acUtils.getFirstPasswordForHost(OLD_DELICIOUS_PW_HOST); if (PREFS.getPrefType(WEBSERVICE_PREF) && PREFS.getCharPref(WEBSERVICE_PREF) == 'delicious') { if (pw) { return true; } else { return false; } } else { // delicious is not configured so we might as well delete the pm entry // since account info is stored in rdf after migration if (pw) { var pm = Components.classes["@mozilla.org/passwordmanager;1"] .getService(Ci.nsIPasswordManager); pm.removeUser(OLD_DELICIOUS_PW_HOST, pw.user); } return false; } } flockDeliciousService.prototype.startMigration = function flockDeliciousService_startMigration(oldVersion, aFlockMigrationProgressListener) { var ctxt = { listener: aFlockMigrationProgressListener }; return { wrappedJSObject: ctxt }; } flockDeliciousService.prototype.finishMigration = function flockDeliciousService_finishMigration(ctxtWrapper) { } flockDeliciousService.prototype.doMigrationWork = function flockDeliciousService_doMigrationWork(ctxtWrapper) { ctxt = ctxtWrapper.wrappedJSObject; if (!ctxt.deliciousMigrator) ctxt.deliciousMigrator = this._migrateDeliciousAccount(ctxt); if (ctxt.deliciousMigrator.next()) ctxt.deliciousMigrator = null; return Boolean(ctxt.deliciousMigrator); } flockDeliciousService.prototype._migrateDeliciousAccount = function flockDeliciousService_migrateDeliciousAccount(ctxt) { this.init(); var pm = Components.classes["@mozilla.org/passwordmanager;1"] .getService(Ci.nsIPasswordManager); var pw = this.acUtils.getFirstPasswordForHost(OLD_DELICIOUS_PW_HOST); // migrate online bm privacy settings PREFS.setBoolPref("flock.favorites.do." + CONTRACT_ID + "--" + pw.user, true); PREFS.setBoolPref("flock.favorites.private." + CONTRACT_ID + "--" + pw.user, PREFS.getBoolPref("flock.favorites.privateByDefault")); var accountURN = "urn:delicious:service:" + pw.user; pm.addUser(accountURN, pw.user, pw.password); pm.removeUser(OLD_DELICIOUS_PW_HOST, pw.user); var acct = this.addAccountById(pw.user, false, null); acct.activate(null); acct.keep(null); yield true; } // END flockIMigratable